package com.iori.common.db.dbctrl;

import java.io.IOException;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * [クラス名]<br>
 * JDBC用SQL保持クラス<br>
 * <br>
 * [機能概要]<br>
 * SQL文をJDBC用にカプセル化したクラス。<br>
 * SQLのパラメータの制御をメインに行う。<br>
 * <br>
 * [変更履歴]<br>
 * 2005/07/30 ver1.00 新規作成 zhanjc<br>
 * <br>
 *
 * @author zhanjc
 * @version 1.00
 */
class PreparedSql {

	/**
	 * ログ出力用のインスタンス生成。
	 */
	protected static Log logger = LogFactory.getLog(PreparedSql.class);

	/**
	 * SQLファイルに書かれているSQL文。
	 */
	protected SqlBrokenModel sqlBrokenObj = null;

	/**
	 * JDBC用に変換されたSQL文。
	 */
	private String sql = null;

	/**
	 * パラメータ名を順番通りに保持する配列
	 */
	private List paraList = null;

	/**
	 * コンストラクタ。<br>
	 * SQLファイルに書かれているオリジナルのSQL文と各キーワード（正規表現文字）を指定する。
	 *
	 * @param originalSql
	 *            オリジナルのSQL文
	 */
	PreparedSql(SqlBrokenModel sqlBrokenObj) {
		this.setSqlBrokenObj(sqlBrokenObj);
	}

	/**
	 * オリジナルのSQL文をJDBC用に変換する。
	 */
	void prepareParameter(Map pList) {
		logger.debug("prepareParameter():perpare parameter begin");
		paraList = new ArrayList();
		StringBuffer sqlSb = new StringBuffer("");
		List sqlBrokenList = sqlBrokenObj.getSqlSegmentList();
		for(int i = 0; i < sqlBrokenList.size(); i++){
			SqlElementModel sqlElementObj = (SqlElementModel)sqlBrokenList.get(i);
			if(sqlElementObj.getType() == SqlElementModel.TYPE_NORMAL){	//「:」
				perpareSqlElement(sqlSb,sqlElementObj.getSqlContent(), sqlElementObj.getSqlParaName(), pList);
			}else if(sqlElementObj.getType() == SqlElementModel.TYPE_NAKAKAKKO){	//「#」
				String paraName = sqlElementObj.getConditionName();
				Object paraValue = pList.get(paraName);
				if(paraValue != null){
					sqlSb.append(paraValue);
				}
			}else{		//「{}」
				String conditionName = sqlElementObj.getConditionName();
				Object conditionValue = pList.get(conditionName);
				if(conditionValue != null){
					Map conditionPara = (Map)conditionValue;
					List sqlElements = sqlElementObj.getSqlElements();
					for(int j = 0 ; j < sqlElements.size(); j++){
						SqlElementModel sqlElementTemp = (SqlElementModel)sqlElements.get(j);
						if(sqlElementTemp.getType() == SqlElementModel.TYPE_NORMAL){	//「:」
							perpareSqlElement(sqlSb, sqlElementTemp.getSqlContent(), sqlElementTemp.getSqlParaName(), conditionPara);
						}else{		//「#」
							String paraName = sqlElementTemp.getConditionName();
							Object paraValue = pList.get(paraName);
							if(paraValue != null){	//兼容torque以前的方式
								sqlSb.append(paraValue);
							}else{
								paraValue = conditionPara.get(paraName);
								if(paraValue != null){
									sqlSb.append(paraValue);
								}
							}
						}
					}	//end for
				}
			}
		}
		this.sql = sqlSb.toString();
		logger.debug("prepareParameter() original sql=" + sqlBrokenObj.getOriginalSql());
		logger.debug("prepareParameter() execute sql=" + sql);
		logger.debug("prepareParameter() parameter=" + this.paraList.toString());
		logger.debug("prepareParameter():perpare parameter  end");
	}

	/**
	 * オリジナルのSQL文をJDBC用に変換する。
	 */
	Map prepareStoredParameter(Map pList, Map outputParameterList) {
		logger.debug("prepareStoredParameter():perpare parameter begin");
		Map outPara = new HashMap();
		paraList = new ArrayList();
		StringBuffer sqlSb = new StringBuffer("");
		List sqlBrokenList = sqlBrokenObj.getSqlSegmentList();
		for(int i = 0; i < sqlBrokenList.size(); i++){
			SqlElementModel sqlElementObj = (SqlElementModel)sqlBrokenList.get(i);
			if(sqlElementObj.getType() == SqlElementModel.TYPE_NORMAL){	//「:」
				perpareStoredSqlElement(sqlSb,sqlElementObj.getSqlContent(), sqlElementObj.getSqlParaName(), pList, outputParameterList, outPara);
			}else if(sqlElementObj.getType() == SqlElementModel.TYPE_NAKAKAKKO){	//「#」
				String paraName = sqlElementObj.getConditionName();
				Object paraValue = pList.get(paraName);
				if(paraValue != null){
					sqlSb.append(paraValue);
				}
			}else{		//「{}」
				String conditionName = sqlElementObj.getConditionName();
				Object conditionValue = pList.get(conditionName);
				if(conditionValue != null){
					Map conditionPara = (Map)conditionValue;
					List sqlElements = sqlElementObj.getSqlElements();
					for(int j = 0 ; j < sqlElements.size(); j++){
						SqlElementModel sqlElementTemp = (SqlElementModel)sqlElements.get(j);
						if(sqlElementTemp.getType() == SqlElementModel.TYPE_NORMAL){	//「:」
							perpareStoredSqlElement(sqlSb, sqlElementTemp.getSqlContent(), sqlElementTemp.getSqlParaName(), conditionPara, outputParameterList, outPara);
						}else{		//「#」
							String paraName = sqlElementTemp.getConditionName();
							Object paraValue = pList.get(paraName);
							if(paraValue != null){	//兼容torque以前的方式
								sqlSb.append(paraValue);
							}else{
								paraValue = conditionPara.get(paraName);
								if(paraValue != null){
									sqlSb.append(paraValue);
								}
							}
						}
					}	//end for
				}
			}
		}
		this.sql = sqlSb.toString();
		logger.debug("prepareStoredParameter() original sql=" + sqlBrokenObj.getOriginalSql());
		logger.debug("prepareStoredParameter() execute sql=" + sql);
		logger.debug("prepareStoredParameter() parameter=" + this.paraList.toString());
		logger.debug("prepareStoredParameter():perpare parameter  end");
		return outPara;
	}

	private void perpareStoredSqlElement(StringBuffer sqlSb,String sqlContent, List sqlParaName, Map pList , Map outputParameterList, Map outPara){
		sqlSb.append(sqlContent);
		for(int i = 0 ; i < sqlParaName.size(); i++){
			String paraName = (String)sqlParaName.get(i);
			if(outputParameterList.containsKey(paraName)){
				outPara.put(paraName , String.valueOf(paraList.size()));
				this.paraList.add(outputParameterList.get(paraName));
			}else{
				this.paraList.add(pList.get(paraName));
			}
		}
	}

	private void perpareSqlElement(StringBuffer sqlSb,String sqlContent, List sqlParaName, Map pList){
		sqlSb.append(sqlContent);
		for(int i = 0 ; i < sqlParaName.size(); i++){
			String paraName = (String)sqlParaName.get(i);
			this.paraList.add(pList.get(paraName));
		}
	}

	/**
	 * SQLのパラメータを設定する。
	 *
	 * @param statement
	 *            実際にパラメータをセットするJDBCのPreparedStatement
	 * @param parameterList
	 *            パラメータ名とその値のマップ
	 *
	 * @exception SQLException
	 *                SQLエラー
	 * @exception IOException
	 *                パラメータにBinaryStream型ある場合に発生する可能性があるエラー
	 */
	String setParameter(PreparedStatement statement, Map parameterList) throws SQLException, IOException {

		//debugLog.debug("SQL=" + this.sql);
		// SQL全文を格納
		if (this.paraList.size() ==0)
			return "";

		PreparedStatementValueSetter psvs = new PreparedStatementValueSetter(statement);
		for (int i = 0; i < this.paraList.size(); i++) {
			Object obj = this.paraList.get(i);
			psvs.set(i + 1, obj);
		}
		//debugLog.debug(sql);
		return sql;
	}

	/**
	 * SQLのパラメータを設定する。
	 *
	 * @param statement
	 *            実際にパラメータをセットするJDBCのPreparedStatement
	 * @param parameterList
	 *            パラメータ名とその値のマップ
	 *
	 * @exception SQLException
	 *                SQLエラー
	 * @exception IOException
	 *                パラメータにBinaryStream型ある場合に発生する可能性があるエラー
	 */
	String setStoredParameter(CallableStatement statement, Map parameterList, Map outPara) throws SQLException, IOException {

		//debugLog.debug("SQL=" + this.sql);
		// SQL全文を格納
		if (this.paraList.size() ==0)
			return "";

		PreparedStatementValueSetter psvs = new PreparedStatementValueSetter(statement);
		for (int i = 0; i < this.paraList.size(); i++) {
			Object obj = this.paraList.get(i);
			if(outPara.containsValue(String.valueOf(i))){
				statement.registerOutParameter(i + 1, ((Integer) obj).intValue());
			}else{
				psvs.set(i + 1, obj);
			}
		}
		//debugLog.debug(sql);
		return sql;
	}

	/**
	 * SQL実行後、OUTパラメータをパラメータリストに設定する。
	 *
	 * @param statement
	 *            実際にパラメータをセットするJDBCのCallableStatement
	 * @param parameterList
	 *            OUTパラメータをセットするパラメータリスト
	 * @param outputParameterList
	 *            OUTパラメータパラメータ名とその値のマップ
	 *
	 * @exception SQLException
	 *                SQLエラー
	 */
	void getOutputParameter(CallableStatement statement, Map parameterList, Map outputParameterList , Map outPara) throws SQLException {
		if (this.paraList.size() ==0)
			return;
		Iterator outputParaIte = outPara.keySet().iterator();
		while(outputParaIte.hasNext()){
			String paraName = (String)outputParaIte.next();
			Object value = statement.getObject(Integer.valueOf((String)outPara.get(paraName))+1);
			outputParameterList.put(paraName, value);
			parameterList.put(paraName, value);
		}
	}


	/**
	 * JDBC用に変換されたSQL文を取得する。
	 *
	 * @return String JDBC用に変換されたSQL文
	 */
	String getSql() {
		return this.sql;
	}

	/**
	 * SQLファイルに書かれているオリジナルのSQL文を設定する。
	 *
	 * @param originalSql
	 *            オリジナルのSQL文
	 */
	void setSqlBrokenObj(SqlBrokenModel sqlBrokenObj) {
		this.sqlBrokenObj = sqlBrokenObj;
	}

	/**
	 * SQL文とパラメータを書き出す。
	 */
	public String toString() {
		StringBuffer result = new StringBuffer("SQL="+this.sql);
		result.append("\n");
		return result.toString();
	}
}

/**
 * [クラス名]<br>
 * パラメータセッタークラス<br>
 * <br>
 * [機能概要]<br>
 * パラメータの設定を暗黙的に行うためのユーティリティクラス。<br>
 * <br>
 * [変更履歴]<br>
 * 2002/10/30 ver1.00 新規作成 M.Komura<br>
 * <br>
 *
 * @author M.Komura
 * @version 1.00
 */
class PreparedStatementValueSetter {

	/**
	 * ログ出力用のインスタンス生成。
	 */
	protected static Log logger = LogFactory.getLog(PreparedStatementValueSetter.class);

	/**
	 * パラメータを設定するPreparedStatement。
	 */
	private PreparedStatement statement;

	/**
	 * コンストラクタ。<br>
	 * PreparedStatementを設定する。
	 *
	 * @param statement
	 *            パラメータを設定するPreparedStatement
	 */
	PreparedStatementValueSetter(PreparedStatement statement) {
		this.statement = statement;
	}

	/**
	 * パラメータを設定する。
	 *
	 * @param index
	 *            設定するパラメータのインデックス番号
	 * @param value
	 *            設定するパラメータ値
	 *
	 * @exception SQLException
	 *                SQLエラー
	 * @exception IOException
	 *                パラメータにBinaryStream型ある場合に発生する可能性があるエラー
	 */
	void set(int index, Object value) throws SQLException, IOException {

		if (value == null) {
			try {
				logger.debug("try setNull(Types.VARCHAR)");
				this.statement.setNull(index, Types.VARCHAR);
			} catch (SQLException se) {
				logger.error("catch SQLException:" + se.getMessage());
				logger.debug("try setNull(Types.NUMERIC)");
				this.statement.setNull(index, Types.NUMERIC);
			}
		} else if (value instanceof java.lang.String) {
			this.statement.setString(index, (java.lang.String) value);
		} else if (value instanceof java.sql.Date) {
			this.statement.setDate(index, (java.sql.Date) value);
		} else if (value instanceof java.sql.Time) {
			this.statement.setTime(index, (java.sql.Time) value);
		} else if (value instanceof java.sql.Timestamp) {
			this.statement.setTimestamp(index, (java.sql.Timestamp) value);
			// } else if(value instanceof java.net.URL) {
			// this.statement.setURL(index, (java.net.URL)value);
		}else if (value instanceof java.lang.Integer) {
			this.statement.setInt(index, ((java.lang.Integer) value).intValue());
		} else if (value instanceof java.math.BigDecimal) {
			this.statement.setBigDecimal(index, (java.math.BigDecimal) value);
		} else if (value instanceof java.lang.Double) {
			this.statement.setDouble(index, ((java.lang.Double) value).doubleValue());
		} else if (value instanceof java.lang.Float) {
			this.statement.setFloat(index, ((java.lang.Float) value).floatValue());
		} else if (value instanceof java.lang.Long) {
			this.statement.setLong(index, ((java.lang.Long) value).longValue());
		} else if (value instanceof java.lang.Boolean) {
			this.statement.setBoolean(index, ((java.lang.Boolean) value).booleanValue());
		} else if (value instanceof java.lang.Byte) {
			this.statement.setByte(index, ((java.lang.Byte) value).byteValue());
		} else if (value instanceof java.lang.Short) {
			this.statement.setShort(index, ((java.lang.Short) value).shortValue());
		} else if (value instanceof java.sql.Array) {
			this.statement.setArray(index, (java.sql.Array) value);
		} else if (value instanceof java.io.InputStream) {
			this.statement.setBinaryStream(index, (java.io.InputStream) value, ((java.io.InputStream) value).available());
		} else if (value instanceof java.sql.Blob) {
			this.statement.setBlob(index, (java.sql.Blob) value);
		}  else if (value instanceof byte[]) {
			this.statement.setBytes(index, (byte[]) value);
		} else if (value instanceof java.sql.Clob) {
			this.statement.setClob(index, (java.sql.Clob) value);
		} else if (value instanceof java.sql.Ref) {
			this.statement.setRef(index, (java.sql.Ref) value);
		}  else {
			this.statement.setObject(index, value);
		}
	}
}
